use anyhow::{anyhow, Result};
use reqwest::{header::CONTENT_TYPE, Client};
use serde::{Deserialize, Serialize};

const PLAYGROUND_URL: &str = "https://play.rust-lang.org/execute";

// This struct will be serialized to JSON before making the API call
#[allow(non_snake_case)]
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
struct RequestPayload {
    channel: String,
    mode: String,
    edition: String,
    crateType: String,
    tests: bool,
    backtrace: bool,
    code: String,
}

#[derive(Debug, Deserialize)]
struct ResponsePayload {
    stdout: Option<String>,
    stderr: Option<String>,
}

// Implementation of RequestPayload with a constructor function for easy creation
impl RequestPayload {
    fn new(code: &str, verify_type: VerifyType) -> RequestPayload {
        let crate_type = match verify_type {
            VerifyType::BUILD => "lib".to_owned(),
            VerifyType::RUN => "bin".to_owned(),
            VerifyType::TEST => todo!(),
        };

        RequestPayload {
            channel: "stable".into(),
            mode: "debug".into(),
            edition: "2021".into(),
            crateType: crate_type,
            tests: false,
            backtrace: false,
            code: code.to_string(),
        }
    }
}

pub enum VerifyType {
    BUILD,
    RUN,
    TEST,
}

#[derive(PartialEq, Debug)]
pub enum VerifyResp {
    Success { stdout: String, stderr: String },
    Fail(String),
}

async fn playground(raw_code: &str, verify_type: VerifyType) -> Result<ResponsePayload> {
    let code = raw_code.trim();
    let request_payload = RequestPayload::new(code, verify_type);

    let client = Client::new();
    client
        .post(PLAYGROUND_URL)
        .header(CONTENT_TYPE, "application/json")
        .json(&request_payload)
        .send()
        .await?
        .json::<ResponsePayload>()
        .await
        .map_err(|e| anyhow!(e.to_string()))
}

pub async fn run_rustycli(raw_code: &str, verify_type: VerifyType) -> VerifyResp {
    match playground(raw_code, verify_type).await {
        Ok(resp) => {
            // math Finished from std.err
            match resp.stderr {
                Some(stderr) => {
                    if stderr.contains("Finished") {
                        return VerifyResp::Success {
                            stdout: resp.stdout.unwrap_or_default(),
                            stderr,
                        };
                    } else {
                        return VerifyResp::Fail(stderr);
                    }
                }
                None => return VerifyResp::Fail("No stderr".to_string()),
            }
        }
        Err(e) => VerifyResp::Fail(e.to_string()),
    }
}

// Capture Rust Code from LLM Output
pub fn capture_rust_code(input: &str) -> Option<String> {
    let regex = regex::Regex::new(r#"```rust\s*([\s\S]*?)```"#).unwrap();
    match regex.captures(&input) {
        Some(captures) => match captures.get(1) {
            Some(code) => return Some(code.as_str().to_string().replace("\\", "")),
            None => return None,
        },
        None => return None,
    }
}

// Capture Json  from LLM Output
pub fn capture_json_code(input: &str) -> Option<String> {
    let regex = regex::Regex::new(r#"```json\s*([\s\S]*)```"#).unwrap();
    match regex.captures(&input) {
        Some(captures) => match captures.get(1) {
            Some(code) => return Some(code.as_str().to_string()),
            None => return None,
        },
        None => return None,
    }
}

#[cfg(test)]
mod test {
    use crate::agent::tools::llm_helper::{clean_and_fix_json, decode_llm_output};
    use crate::agent::tools::playground::{capture_json_code, capture_rust_code, VerifyType};
    use regex::Regex;
    use serde::{Deserialize, Serialize};

    #[tokio::test]
    async fn test_run_rustycli() {
        let code = r#"
            fn main() {
                println!("Hello, World!");
            }
            "#;
        let result = super::run_rustycli(code, VerifyType::RUN).await;
        eprintln!("{result:?}")
    }

    #[tokio::test]
    async fn parse_rust_code() {
        let raw_code = tokio::fs::read_to_string("./riir.archive").await.unwrap();
        let code = capture_rust_code(&raw_code).unwrap();
        eprintln!("{:#?}", code);
        let result = super::run_rustycli(&code, VerifyType::BUILD).await;
        eprintln!("{result:#?}")
    }

    // #[tokio::test]
    // async fn parse_json_code() {
    //     let raw_code = tokio::fs::read_to_string("./riir.archive").await.unwrap();
    //     let json_str = clean_and_fix_json(&raw_code);
    //     eprintln!("{:#?}", json_str);

    //     let cleaned_json_str = decode_llm_output(&json_str);
    //     eprintln!("{:#?}", cleaned_json_str);
    // }
}
